{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# PL access to PS DRAM \n",
    "\n",
    "In this notebook, the PYNQ Xlnk class will be used to allocate a memory buffer in the DDR memory. The physical address of the memory will be passed to the PL, in this case to an IOP. The IOP has a connection to the PS DRAM. An application will run on the IOP to modify the contents of the memory buffer in the PS DRAM. \n",
    "\n",
    "In a similar way, another IP in the PL can use a physical memory pointer to access PS DRAM. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create an instance of Xlnk"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pynq import Xlnk\n",
    "xlnk = Xlnk()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Check the status of the mmu\n",
    "\n",
    "*cma_stats()* can be used to get the status of the Xlnk instance. Xlnk can only allocate memory if sufficient space is available. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xlnk.cma_stats()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Allocate memory buffer\n",
    "The cma_array() function in Xlnk creates a contiguous memory buffer. The buffer can be used from Python. Python is running on Linux, and will access the buffer via a virtual memory address. This is transparent to the user at the Python level. The Xlnk  function cma_get_phy_addr() can be used to return the physical address. This physical address can be used by IP in the PL to access the same memory buffer in PS DRAM. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np \n",
    "py_buffer = xlnk.cma_array(shape=(1000,), dtype=np.int32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Check the memory buffer addresses\n",
    "\n",
    "The virtual address can be used by any application running in Linux. This could be a Python application, or a C/C++ or other application running in Linux. The Physical address can be passed to an IP block in an overlay."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Download the base overlay"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pynq.overlays.base import BaseOverlay\n",
    "base = BaseOverlay('base.bit')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create MicroBlaze program\n",
    "\n",
    "The C code for a new function that will run on a MicroBlaze is provided in the next cell. The C function parameters are a physical address, a length, and data. The function will modify the contents of the It will modify data in the range [*address* : *address*+*length*], by reading the contents of each memory location, and adding an offset value *data* to each location."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%microblaze base.ARDUINO\n",
    "void my_function(unsigned int physical_address, unsigned int length, unsigned int data) {\n",
    "    int i;\n",
    "    int *mb_buffer;\n",
    "    \n",
    "    // DDR is accessed through a GP port at offset 0x20000000\n",
    "    mb_buffer = (int *)(physical_address|0x20000000); // Cast to pointer and convert to DDR offset address\n",
    "\n",
    "    // Write memory buffer in DDR\n",
    "    for(i=0; i<length; i++){\n",
    "        mb_buffer[i]= mb_buffer[i] + data;\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Initialise the buffer with some values: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "length = 20 \n",
    "for i in range(length):\n",
    "    py_buffer[i] = i"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Check the content of the buffer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "py_buffer[0:length]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Write to buffer from IOP\n",
    "\n",
    "Write the physical pointer address returned form the *mmu* Xlnk instance, along with an initialization value and a length. The IOP application will then write to the memory buffer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = 10\n",
    "my_function(py_buffer.physical_address, length, data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Check the contents of the buffer after the IOP application has modified the buffer. The cell above can be re-run with different values of data and length."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "py_buffer[0:length]"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
